package com.minelx.sequence.pattern;

import com.minelx.sequence.core.ReturnMatcher;
import com.minelx.sequence.core.StringScanner;
import com.minelx.sequence.pattern.variable.IVariableManager;

import java.util.function.Consumer;

import static com.minelx.sequence.core.StringScanner.source;

public class Pattern {

	private final Match match;

	private Pattern(StringScanner source, IVariableManagerFactory variableManagerFactory) {
		VariableManagerBuilder variableManagerBuilder = new VariableManagerBuilder(variableManagerFactory);
		ReturnMatcher<?, StringScanner> matcher = PatternParser.create(new VariablePatternParserFactory(variableManagerBuilder))
				.get(source).orElseThrow(RuntimeException::new);
		match = new Match(matcher, variableManagerBuilder);
	}

	public IVariableManager match(String actualText) {
		return match(source(actualText));
	}

	public IVariableManager match(StringScanner source) {
		return match.get(source).orElseThrow(RuntimeException::new);
	}

	public ReturnMatcher<IVariableManager, StringScanner> matcher() {
		return match;
	}

	public static Pattern create(String patternAsText) {
		return create(source(patternAsText), IVariableManagerFactory.simple());
	}

	public static Pattern create(StringScanner source) {
		return create(source, IVariableManagerFactory.simple());
	}

	public static Pattern create(StringScanner source, IVariableManagerFactory variableManagerFactory) {
		return new Pattern(source, variableManagerFactory);
	}

	private static final class Match implements ReturnMatcher<IVariableManager, StringScanner> {

		private final ReturnMatcher<?, StringScanner> matcher;

		private final VariableManagerBuilder variableManagerBuilder;

		public Match(ReturnMatcher<?, StringScanner> matcher,
		             VariableManagerBuilder variableManagerBuilder) {
			this.matcher = matcher;
			this.variableManagerBuilder = variableManagerBuilder;
		}

		public boolean isMatch(StringScanner clue) {
			return matcher.isMatch(clue);
		}

		public IVariableManager onMatched(StringScanner source) {
			matcher.onMatched(source);
			return variableManagerBuilder.build();
		}
	}

	private static final class VariableManagerBuilder implements VariableSettingFactory {

		private final IVariableManagerFactory variableManagerFactory;

		private IVariableManager currentVariableManager;

		public VariableManagerBuilder(IVariableManagerFactory variableManagerFactory) {
			this.variableManagerFactory = variableManagerFactory;
			currentVariableManager = variableManagerFactory.createVariableManager();
		}

		public IVariableManager build() {
			IVariableManager result = currentVariableManager;
			// swap a new instance
			currentVariableManager = variableManagerFactory.createVariableManager();
			return result;
		}

		@Override
		public Consumer<Object> createVariableSetter(String variableName) {
			return as -> currentVariableManager.set(variableName, as);
		}
	}
}
